<?php

DEFINE('DATE_INF', strtotime('1/1/' . (integer) (date('Y') + 2)));

/**
 * Tools_CalendarObject - Classe de calcul des plages et des disponibilités
 *
 * @package
 * @author Alexis Eric, 
 * Erra Prod. http://www.erraprod.com
 * @copyright Copyright (c) 2012 - Erra Prod
 * @access public
 */
class Tools_CalendarObject extends Erra_Fields_Base_Object {

    /**
     * Renvoie une liste, avec répétitions des disponibilités web
     *  Dummy
     * @param type $agendaIdArray
     * @param type $startTime
     * @param type $endTime
     * @return type
     */
    public static function getOpenWebSlots($agendaIdArray, $startTime, $endTime) {
        $openWebSlots = array();
        foreach ($agendaIdArray as $agendaId) {
            $agendas = Erra_Core_DatabasePdo::SELECT('SELECT slot_day, slot_start, slot_end, agenda_id, deleted_occurencies,repeat_enabled , event_type, repeat_occurency,repeat_on_specific_days, repeat_time, repeat_period, repeat_occurency_date FROM msa_slotobject WHERE agenda_id=' . $agendaId . ' AND event_type="web" AND slot_day <=' . $endTime . ' ORDER BY (slot_day+slot_start) ASC');
            if (Erra_Core_DatabasePdo::rowCount()) {
                while ($event = $agendas->fetch()) {
                    $openWebSlots = self::enable_repeat($event, $openWebSlots, null, $startTime, $endTime, true, false);
                }
            }
        }
        return $openWebSlots;
    }

    /**
     * Récupere en temps réel l'ouverture et la fermeture d'un calendrier en fonction des ouverture web et de la durée totale des prestations
     * passée en argument($totalTime)
     * 
     * @param type $startWeek - Jour de démarrage
     * @param type $intervalTime - Durée d'une intervalle (obtenue à partir du fichier de config
     * @param type $totalTime - Durée totale des prestations commandées
     * @param type $openSlotContainer - La liste des ouvertures web
     * @return type
     */
    public static function getOpeningAndClosureTime($startWeek, $intervalTime, $totalTime, $openSlotContainer) {
        $openingTime = 3600 * 4; // From 4 AM
        $closureTime = 3600 * 23; // To 23 AM
        $decay = $closureTime - $openingTime;
        $timeInterval = array();
        for ($i = 0; $i <= $decay / $intervalTime; $i++) {
            $timeInterval[] = $openingTime + ($i * $intervalTime);
        }

        $realOpeningTime = 0;
        $realClosureTime = 0;
        for ($i = 0; $i < count($timeInterval); $i++) {
            for ($day = 0; $day < 7; $day++) {
                $prestationStart = $startWeek + (3600 * 24 * $day) + $timeInterval[$i];
                $time = (time() < $prestationStart && timeMatch($prestationStart, $totalTime, $openSlotContainer)) ? $timeInterval[$i] : '-';

                $realOpeningTime = (is_numeric($time) && ($realOpeningTime == 0 || $realOpeningTime > $time)) ? $time : $realOpeningTime;
                $realClosureTime = (is_numeric($time) && ($realClosureTime == 0 || $realClosureTime < $time)) ? $time : $realClosureTime;
            }
        }

        return array('opening' => $realOpeningTime, 'closure' => $realClosureTime);
    }

    /**
     * Retourne une liste des agendas qui correspondent à une prestation ayant lieu à $time et de durée $duration
     * 
     * @param type $time
     * @param type $duration
     * @param type $openSlotContainer
     * @return string
     */
    function getAgendaMatch($time, $duration, $openSlotContainer) {
        $matchList = '';
        if (is_array($openSlotContainer)) {
            foreach ($openSlotContainer as $agenda_id => $slots) {
                if (is_array($slots)) {
                    foreach ($slots as $slot) {
                        $start = $slot['start'];
                        $end = $slot['end'];
                        if (Tools_CalendarObject::is_between($time, $start, $end, $duration)) {
                            $matchList .= $agenda_id . ';';
                        }
                    }
                }
            }
        }
        return $matchList;
    }

    /**
     * getOpenWebSlotsForDeintersect() - Récupere la liste des plages horaires  comprises entre $startTime et $endTime
     * 
     * @param type $agendaIdArray
     * @param type $startTime
     * @param type $endTime
     * @return type
     */
    public static function getOpenWebSlotsForDeintersect($agendaIdArray, $startTime, $endTime, $prestation_id_list = null) {
        $prestation_id_list = array_filter_numeric($prestation_id_list);
        $openWebSlots = array();
        foreach ($agendaIdArray as $agendaId) {
            $agendas = Erra_Core_DatabasePdo::SELECT('SELECT slot_day, slot_start, slot_end, agenda_id, deleted_occurencies,repeat_enabled , event_type, repeat_occurency,repeat_on_specific_days, repeat_time, repeat_period, repeat_occurency_date, prestation FROM msa_slotobject WHERE agenda_id=' . $agendaId . ' AND event_type="web" AND slot_day <=' . $endTime . ' ORDER BY (slot_day+slot_start) ASC');
            if (Erra_Core_DatabasePdo::rowCount()) {
                $allPrestationExists = true;
                while ($event = $agendas->fetch()) {
                    $allowed_prestations = array_filter_numeric(explode('§', $event->prestation));
                    if (is_array($prestation_id_list) && count($prestation_id_list)) {
                        foreach ($prestation_id_list as $prestation_id) {
                            if (!in_array($prestation_id, $allowed_prestations)) {
                                $allPrestationExists = false;
                            }
                        }
                    }
                    if ($allPrestationExists) {
                        $openWebSlots[$event->agenda_id] = self::enable_repeat($event, $openWebSlots[$event->agenda_id], null, $startTime, $endTime, true, false);
                    }
                }
            }
        }
        return $openWebSlots;
    }

    /**
     * getBusySlots() - Récupère la liste des prestations déja enregistrées
     * 
     * @param type $agendaId
     * @param type $startTime
     * @param type $endTime
     * @return type
     */
    public static function getBusySlots($agendaId, $startTime, $endTime) {
        $busySlots = array();
        $agendas = Erra_Core_DatabasePdo::SELECT('SELECT agenda_id, slot_day, slot_start, slot_end, duree FROM msa_slotobject, msa_prestationobject WHERE agenda_id=' . $agendaId . ' AND event_type="presta" AND (slot_day+slot_start)>=' . $startTime . ' AND (slot_day+slot_end)<=' . $endTime . ' AND msa_prestationobject.id=msa_slotobject.prestation_id ORDER BY (slot_day+slot_start) ASC');

        if (Erra_Core_DatabasePdo::rowCount()) {
            while ($event = $agendas->fetch()) {
                $event->slot_end = ($event->slot_start + ($event->duree * 60));
                $busySlots[] = $event;
            }
        }

        return $busySlots;
    }

    /**
     * slotsDeintersect() - Soustrait les slots occupés des slots libres
     * Retourne une la liste des slots $openSlots liberés des prestations occupant de l'espace ($busySlotsContainer)
     * 
     * @param type $openSlots - La liste des slots libres
     * @param type $busySlotsContainer - La liste des slots occupés
     * @param type $eventLength 
     * @return type
     */
    public static function slotsDeintersect($openSlots, $busySlotsContainer, $eventLength) {
        $openSlotsContainer = array(); // resultat
        // D'abord, on passe tous les slots libres à notre résultat

        foreach ($busySlotsContainer as $agenda_id => $busySlotArray) {
            $openSlotsContainer[$agenda_id] = $openSlots[$agenda_id];
        }

        foreach ($busySlotsContainer as $agenda_id => $busySlotArray) {
            foreach ($busySlotArray as $busySlot) {
                foreach ($openSlotsContainer[$agenda_id] as $key => $openSlot) {
                    $start = $openSlot['start'];
                    $end = $openSlot['end'];
                    $eventStart = $busySlot->slot_day + $busySlot->slot_start;
                    $eventEnd = $busySlot->slot_day + $busySlot->slot_end;
                    if ($eventStart == $start && $eventEnd == $end) {
                        // perfect match
                        unset($openSlotsContainer[$agenda_id][$key]);
                    } else if (Tools_CalendarObject::is_between($eventStart, $start, $end) && Tools_CalendarObject::is_between($eventEnd, $start, $end)) {
                        // Create 2 new slots : [A....[B--]...] = [A....]  [B....]
                        $firstSlot = array('start' => $start, 'end' => $eventStart, 'agenda_id' => $agenda_id);
                        $secondSlot = array('start' => $eventEnd, 'end' => $end, 'agenda_id' => $agenda_id);
                        unset($openSlotsContainer[$agenda_id][$key]);
                        $openSlotsContainer[$agenda_id][] = $firstSlot;
                        $openSlotsContainer[$agenda_id][] = $secondSlot;
                    } else if (Tools_CalendarObject::is_between($eventStart, $start, $end)) {
                        // [A....[B--]---] = [A......]
                        $openSlotsContainer[$agenda_id][$key]['end'] = $eventStart;
                    } else if (Tools_CalendarObject::is_between($eventEnd, $start, $end)) {
                        // [B--A[...]...] = ...[A.....]
                        $openSlotsContainer[$agenda_id][$key]['start'] = $eventEnd;
                    }
                }
            }
        }
        return $openSlotsContainer;
    }

    /**
     * enable_repeat - Calcule les proprietés de répétition pour un une ouverture web ou une plage horaire
     * 
     * @param type $event - L'évènement de la table msa_slotobject
     * @param type $event_list - une liste passée en argument contenant le résultat (concatené avec array_merge)
     * @param type $agendaIdArrayFlipped - Une liste des inversée des agendas
     * @param type $startCalendar - Début de la plage du calendrier que l'on veut afficher (les repetitions affichées ne sortiront pas de de cadre)
     * @param type $endCalendar - Fin de la plage du calendrier que l'on veut afficher (les repetitions affichées ne sortiront pas de de cadre)
     * @param type $webView - True si on est en vue web
     * @param type $forJavascript - True si l'affichage est pour le calendrier javascript (les informations retournées sont plus completes 
     * @return boolean
     */
    public static function enable_repeat($event, $event_list, $agendaIdArrayFlipped, $startCalendar, $endCalendar, $webView = false, $forJavascript = true) {
        $eventStart = ($event->slot_start + $event->slot_day);
        $eventEnd = ($event->slot_end + $event->slot_day);
        $eventLength = $eventEnd - $eventStart;
        $userId = isset($agendaIdArrayFlipped[$event->agenda_id]) ? $agendaIdArrayFlipped[$event->agenda_id] : '';
        // On transforme les occurences supprimées en tableau
        $deleted_occurencies = strlen($event->deleted_occurencies) ? filter_array(explode(';', $event->deleted_occurencies)) : array();
        // On recherche si dans les occurences supprimées il y a une suppression en série (supprimer cet évènement et tous les suivants)
        $serie_deletion_start = find_serie_deletion_starter($deleted_occurencies);
        // On ne conserve finalement que les occurences numérique supprimées
        $deleted_occurencies = array_filter_numeric($deleted_occurencies);
        // On vérifie si l'évènement appartient à l'intervalle de temps 
        if (self::is_between($eventStart, $startCalendar, $endCalendar) || self::is_between($eventEnd, $startCalendar, $endCalendar)) {
            if ($forJavascript == true) {
                $current_slot = array('id' => $event->event_id, 'prestation' => $event->prestation, 'agendaId' => $event->agenda_id, 'start' => $eventStart * 1000, 'end' => $eventEnd * 1000, 'realStart' => $eventStart * 1000, 'title' => $event->title, 'userId' => $userId, 'occurency' => 'original', 'event_type' => $event->event_type, 'color' => $event->slot_color);
            } else {
                $current_slot = array('start' => $eventStart, 'end' => $eventEnd);
            }
            if ($webView == false) {
                $current_slot['free'] = false;
            }
            $event_list[] = $current_slot;
        }
        // Si l'évènement est un slot et répété, on en calcule les limites
        if ($event->repeat_enabled == 'repeat' && ($event->event_type == 'slot' || $event->event_type == 'web')) {
            $limit_sup = DATE_INF; // On démarre avec cette valeur par défaut repeat_occurency == 'always'

            if ($event->repeat_occurency == 'specific_date') {
                $limit_sup = strtotime(date_euro_to_unix_format($event->repeat_occurency_date));
            }

            if ($event->repeat_occurency == 'while') {
                if ($event->repeat_period == 'jour') {
                    $step_length = (3600 * 24);
                }
                if ($event->repeat_period == 'semaine') {
                    $step_length = (3600 * 24 * 7);
                }
                if ($event->repeat_period == 'mois') {
                    $step_length = (3600 * 24 * 31);
                }
                if ($event->repeat_period == 'an') {
                    $step_length = (3600 * 24 * 365);
                }
                $limit_sup = ($step_length) + $eventStart;
            }
            // A ce stade, l'évènement ou une de ses répétitions a de fortes chances d'appartenir à notre tranche de calcul
            // On va donc simuler les répétitions du début de l'évènement jusqu'à l'intervalle comprise pour ajouter, si nécessaire l'évènement
            // à notre intervalle
            if ($limit_sup > $startCalendar && $eventStart < $endCalendar) {
///////////////////////////
// REPEAT PERIOD = JOUR
///////////////////////////
                if ($event->repeat_period == 'jour') {
                    // Event is always repeated
                    ///////////////////////////
                    // Define end of calculation
                    if ($event->repeat_occurency == 'always') {
                        $endEventDisplay = $endCalendar;
                    } else if ($event->repeat_occurency == 'specific_date') {
                        $endEventDisplay = strtotime(date_euro_to_unix_format($event->repeat_occurency_date));
                        $endEventDisplay = ($endEventDisplay > $endCalendar) ? $endCalendar : $endEventDisplay; // Saves computing time
                    }

                    $step_length = (3600 * 24) * $event->repeat_time; // repeat_time = "1 jour", "2 jours" ... "n jours"
                    $repeatStart = $eventStart + $step_length;
                    $repeatEnd = $repeatStart + $eventLength;
                    $occurency = 0;
                    // Cette boucle démarre du début de l'évènement jusqu'à la fin de notre affichage et va intercepter 
                    // Les évènements compris dans notre zone d'affichage
                    while ($repeatStart <= $endEventDisplay) {
                        if (self::is_between($repeatStart, $startCalendar, $endEventDisplay) || self::is_between($repeatEnd, $startCalendar, $endEventDisplay)) {
                            if (!in_array($occurency, $deleted_occurencies) &&
                                    !($serie_deletion_start > -1 && $serie_deletion_start < $occurency)) {
                                if ($forJavascript == true) {
                                    $repeat_slot = array('id' => $event->event_id, 'prestation' => $event->prestation, 'agendaId' => $event->agenda_id, 'start' => $repeatStart * 1000, 'end' => $repeatEnd * 1000, 'realStart' => $eventStart * 1000, 'title' => $event->title, 'userId' => $userId, 'occurency' => $occurency, 'event_type' => $event->event_type, 'color' => $event->slot_color);
                                } else {
                                    $repeat_slot = array('start' => $repeatStart, 'end' => $repeatEnd);
                                }
                                if ($webView == false) {
                                    $repeat_slot['free'] = false;
                                }
                                $event_list[] = $repeat_slot;
                            }
                        }
                        $repeatStart+=$step_length;
                        $repeatEnd = $repeatStart + $eventLength;
                        $occurency++;
                    }
                }
///////////////////////////
// REPEAT PERIOD = SEMAINE
///////////////////////////
                if ($event->repeat_period == 'semaine') {
                    if ($event->repeat_occurency == 'always') {
                        $endEventDisplay = $endCalendar;
                    } else if ($event->repeat_occurency == 'specific_date') {
                        $endEventDisplay = strtotime(date_euro_to_unix_format($event->repeat_occurency_date));
                        $endEventDisplay = ($endEventDisplay > $endCalendar) ? $endCalendar : $endEventDisplay; // Saves computing time
                    }
                    $day_list = strlen($event->repeat_on_specific_days) ? explode('§', $event->repeat_on_specific_days) : array();

                    if (count($day_list)) {
                        $repeatStart = ($event->slot_day + $event->slot_start);
                        $repeatEnd = $repeatStart + $eventLength;
                        $occurency = 0;
                        $indexWeek = 0;
                        while ($repeatStart <= $endEventDisplay) {
                            if (!($indexWeek % $event->repeat_time)) {
                                for ($i = 0; $i < count($day_list); $i++) {
                                    $repeatStart = (self::_index_to_time($day_list[$i], $indexWeek, $startCalendar) + $event->slot_start);
                                    $repeatEnd = $repeatStart + $eventLength;

                                    if ($repeatStart > $eventStart && (self::is_between($repeatStart, $startCalendar, $endEventDisplay) || self::is_between($repeatEnd, $startCalendar, $endEventDisplay))) {
                                        if (!count($deleted_occurencies) || !in_array($occurency, $deleted_occurencies)) {
                                            if ($forJavascript == true) {
                                                $repeat_slot = array('id' => $event->event_id, 'prestation' => $event->prestation, 'agendaId' => $event->agenda_id, 'start' => $repeatStart * 1000, 'end' => $repeatEnd * 1000, 'realStart' => $eventStart * 1000, 'title' => $event->title, 'userId' => $userId, 'occurency' => $occurency, 'color' => $event->slot_color, 'event_type' => $event->event_type);
                                            } else {
                                                $repeat_slot = array('start' => $repeatStart, 'end' => $repeatEnd);
                                            }
                                            if ($webView == false) {
                                                $repeat_slot['free'] = false;
                                            }
                                            $event_list[] = $repeat_slot;
                                        }
                                    }
                                    $occurency++;
                                }
                            }
                            $indexWeek++;
                            $repeatStart = (self::_index_to_time($day_list[0], $indexWeek, $event->slot_day) + $event->slot_start);
                            $repeatEnd = $repeatStart + $eventLength;
                        }
                    }
                }
////////////////////////////////
// REPEAT PERIOD = MOIS OU AN
///////////////////////////////
                if ($event->repeat_period == 'mois' || $event->repeat_period == 'an') {
                    // Event is always repeated
                    ///////////////////////////
                    // Define end of calculation
                    if ($event->repeat_occurency == 'always') {
                        $endEventDisplay = $endCalendar;
                    } else if ($event->repeat_occurency == 'specific_date') {
                        $endEventDisplay = strtotime(date_euro_to_unix_format($event->repeat_occurency_date));
                        $endEventDisplay = ($endEventDisplay > $endCalendar) ? $endCalendar : $endEventDisplay; // Saves computing time
                    }

                    $strtotimeNeedle = ($event->repeat_period == 'mois') ? 'month' : 'year';
                    $repeatStart = (strtotime('this ' . $strtotimeNeedle, $event->slot_start));
                    $repeatEnd = $repeatStart + $eventLength;
                    $occurency = 0;
                    $indexTime = -1;
                    while ($repeatStart <= $endEventDisplay) {
                        if (!($indexTime % $event->repeat_time)) {
                            if ($repeatStart > $eventStart && (self::is_between($repeatStart, $startCalendar, $endEventDisplay) || self::is_between($repeatEnd, $startCalendar, $endEventDisplay))) {
                                if (!in_array($occurency, $deleted_occurencies)) {
                                    if ($forJavascript == true) {
                                        $repeat_slot = array('id' => $event->event_id, 'prestation' => $event->prestation, 'agendaId' => $event->agenda_id, 'start' => $repeatStart * 1000, 'end' => $repeatEnd * 1000, 'realStart' => $eventStart * 1000, 'title' => $event->title, 'userId' => $userId, 'occurency' => $occurency, 'color' => $event->slot_color, 'event_type' => $event->event_type);
                                    } else {
                                        $repeat_slot = array('start' => $repeatStart, 'end' => $repeatEnd);
                                    }
                                    if ($webView == false) {
                                        $repeat_slot['free'] = false;
                                    }
                                    $event_list[] = $repeat_slot;
                                }
                            }
                        }
                        $occurency++;
                        $indexTime++;
                        $repeatStart = (strtotime('+' . $indexTime . ' ' . $strtotimeNeedle . ' ', $event->slot_day) + $event->slot_start);
                        $repeatEnd = $repeatStart + $eventLength;
                    }
                }
            }
        }
        return $event_list;
    }

    /**
     * _index_to_time() - Fonction utilitaire calculant un décalage dans la date, par exemple
     * _index_to_time('monday', 3, strtotime(date('10-12-2001')) retournera le timestamp du premier lundi dans 3 semaines à partir du 10-12-2001
     * 
     * @param type $dayString - Le jour cible
     * @param type $weekIndex - Nombre de semaine de décalage
     * @param type $time - Le temps de référence
     * @return type
     */
    public static function _index_to_time($dayString, $weekIndex, $time) {
        $list = array('monday' => 0, 'tuesday' => 1, 'wednesday' => 2, 'thursday' => 3, 'friday' => 4, 'saturday' => 5, 'sunday' => 6);
        $dayPadding = (3600 * 24) * $list[$dayString];
        $weekPadding = (3600 * 24 * 7) * $weekIndex;
        $slot_monday = strtotime(date('o-\\WW', $time));
        $padding = $dayPadding + $weekPadding;
        return $slot_monday + $padding;
    }

    /**
     * is_between - Retourne true si le nombre $item est entre $start et $end, éventuellement avec une durée $duration précisée
     * 
     * @param type $item
     * @param type $start
     * @param type $end
     * @param type $duration
     * @return type
     */
    public static function is_between($item, $start, $end, $duration = null) {
        if ($duration == null) {
            return ($item >= $start && $item <= $end) ? true : false;
        }
        return ($item >= $start && $item + $duration <= $end) ? true : false;
    }

}
